using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using CoreDC;
using Chimera2.HUI;
using Chimera2.Classes;
using CoreDC.Classes;

namespace Chimera2
{
    public class Main
    {
        #region Variables

        System.Windows.Forms.Timer reconnectTimer;
        public Settings settings;
        public Hashtable reconnects;
        CommandHandler command;
        public Dictionary<Guid, DcClient> clients;
        readonly Front front;

        #endregion

        #region Constructor

        public Main(Front front)
        {
            this.front = front;

            clients = new Dictionary<Guid, DcClient>();

            // Initializing Chimera2..
            settings = new Settings();
            reconnects = new Hashtable();
            command = new CommandHandler(this);

            Datahandler dh = new Datahandler();
            settings = dh.LoadSettings();

            reconnectTimer = new System.Windows.Forms.Timer();
            reconnectTimer.Interval = 30000;
            reconnectTimer.Tick += new EventHandler(reconnectTimer_Tick);
            reconnectTimer.Start();
        }

        #endregion

        #region DcClient events

        /// <summary>
        /// The DcClient has been connected
        /// </summary>
        /// <param name="sender">DcClient</param>
        private void dcClient_OnConnected(DcClient sender)
        {
            Guid guid = (Guid)sender.ClientInfo.Data;

            if (reconnects.ContainsKey(guid))
            {
                reconnects.Remove(guid);
            }

            settings.hubs[guid].IsConnected = true;
            settings.hubs[guid].IsManualDisconnect = false;

            front.UpdateStatusWindow("[STATUS_MESSAGE] I'm connected! :)", guid);
        }

        /// <summary>
        /// The DcClient has been disconnected
        /// </summary>
        /// <param name="sender">DcClient</param>
        private void dcClient_OnDisconnected(DcClient sender)
        {
            Guid guid = (Guid)sender.ClientInfo.Data;
            settings.hubs[guid].IsConnected = false;
            front.UpdateStatusWindow("[STATUS_MESSAGE] I was disconnected.. :(", guid);

            if (!settings.hubs[guid].IsManualDisconnect)
            {
                reconnects[guid] = 1;
            }
        }

        /// <summary>
        /// The DcClient has recieved a private message
        /// </summary>
        /// <param name="sender">DcClient</param>
        /// <param name="user">User sending the PM</param>
        /// <param name="text">The contents of the PM</param>
        private void dcClient_OnPrivateMessageRecieved(DcClient sender, User user, string text)
        {
            // The big one, need to first check credentials, and then what user wants to do.
            Guid guid = (Guid)sender.ClientInfo.Data;
            Hub hub = settings.hubs[guid];

            Access access = CheckAccess(hub, user.Username);

            //string response = command.Process(text, user.Username, access);
            //sender.SendPrivateMessage(user, response);

            front.UpdateStatusWindow("[PRIVATE_MESSAGE] <" + user.Username + "> " + text, guid);
            sender.SendPrivateMessage(user, "Hi " + access.ToString() + " " + user.Username + "! I am " + sender.ClientInfo.Username + ". I want to be your friend ^_^");
        }

        /// <summary>
        /// The DcClient has recieved a mainchat message
        /// </summary>
        /// <param name="sender">DcClient</param>
        /// <param name="user">User sending the mainchat message</param>
        /// <param name="text">The contents of the mainchat message</param>
        /// <param name="ownMessage">Is this our own message</param>
        private void dcClient_OnMainchatMessage(DcClient sender, User user, string text, bool ownMessage)
        {
            Guid guid = (Guid)sender.ClientInfo.Data;
            Hub hub = settings.hubs[guid];

            Access access = CheckAccess(hub, user.Username);

            front.UpdateStatusWindow("[" + access.ToString() + "] <" + user.Username + "> " + text, guid);
        }

        private void dcClient_OnMessageReceived(DcClient sender, string text)
        {
            Guid guid = (Guid)sender.ClientInfo.Data;

            front.UpdateStatusWindow(text, guid);
        }

#endregion

        #region Timer

        /// <summary>
        /// Handles the Tick event of the reconnectTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void reconnectTimer_Tick(object sender, EventArgs e)
        {
            Hashtable reCopy = (Hashtable)reconnects.Clone();

            foreach (Guid guid in reCopy.Keys)
            {
                if ((int)reCopy[guid] == 0)
                {
                    try
                    {
                        clients[guid].Connect();
                    }
                    catch
                    {
                        // Connection failed, hubs is probably not online. We'll try again in 3 minutes
                        reconnects[guid] = (int)reconnects[guid] + 3;
                    }
                }
                else
                {
                    // We have longer to wait, try again in T minus 1
                    reconnects[guid] = (int)reconnects[guid] - 1;
                }
            }
        }

        #endregion

        #region Hub actions

        /// <summary>
        /// Connect to all Active hubs
        /// </summary>
        public void ConnectAllHubs()
        {
            foreach (Hub hub in settings.hubs.Values)
            {
                try
                {
                    if (clients.ContainsKey(hub.Guid))
                    {
                        if (hub.Active && !hub.IsConnected)
                        {
                            clients[hub.Guid].Connect();
                        }
                    }
                    else
                    {
                        DcClient client = DcClientFactory(hub);
                        if (hub.Active && !hub.IsConnected)
                        {
                            client.Connect();
                            clients.Add(hub.Guid, client);
                        }
                    }
                }
                catch (Exception e)
                {
                    front.UpdateStatusWindow("[CONNECTION_ERROR] " + e.Message, hub.Guid);
                }
            }
        }

        /// <summary>
        /// Connect to specific hub based on Guid
        /// </summary>
        /// <param name="guid">Hub Guid</param>
        public void ConnectHub(Guid guid)
        {
            Hub hub = settings.hubs[guid];

            if (clients.ContainsKey(guid))
            {
                if (hub.IsConnected)
                {
                    clients[guid].Disconnect();
                }
                clients[guid].Connect();
            }
            else
            {
                DcClient client = DcClientFactory(hub);
                try
                {
                    client.Connect();
                    clients.Add(guid, client);
                }
                catch (Exception e)
                {
                    front.UpdateStatusWindow("[CONNECTION_ERROR] " + e.Message, guid);
                }
            }            
        }


        /// <summary>
        /// Disconnect all hubs
        /// </summary>
        public void DisconnectAllHubs()
        {
            List<Guid> rem = new List<Guid>();

            foreach (DcClient dcClient in clients.Values)
            {
                Guid guid = (Guid)dcClient.ClientInfo.Data;
                settings.hubs[guid].IsManualDisconnect = true;
                dcClient.Disconnect();
                dcClient.Dispose();
            }
            clients.Clear();
        }

        /// <summary>
        /// Disconnect specific hub
        /// </summary>
        /// <param name="guid">Hub Guid</param>
        public void DisconnectHub(Guid guid)
        {
            settings.hubs[guid].IsManualDisconnect = true;
            clients[guid].Disconnect();
            clients[guid].Dispose();
            clients.Remove(guid);
        }

        /// <summary>
        /// Check if bot is connected to specified hub
        /// </summary>
        /// <param name="guid">The Guid of the hub</param>
        /// <returns>Whether the hub is connected or not</returns>
        public bool IsConnected(Guid guid)
        {
            return settings.hubs[guid].IsConnected;
        }

        #endregion

        #region General functions

        /// <summary>
        /// Checks the users access
        /// </summary>
        /// <param name="hub">The hub</param>
        /// <param name="UserName">Name of the user</param>
        /// <returns>Admin, Editor or Regular</returns>
        public Access CheckAccess(Hub hub, string UserName)
        {
            if (FindAdmin(UserName, hub.Guid) != null)
            {
                return Access.Admin;
            }
            
            if (FindEditor(UserName, hub.Guid) != null)
            {
                return Access.Editor;
            }
            
            return Access.Regular;
        }

        /// <summary>
        /// Creates a DcClient instance
        /// </summary>
        /// <param name="hub">The hub</param>
        /// <returns>A properly instanced DcClient</returns>
        public DcClient DcClientFactory(Hub hub)
        {
            DcClient dcClient = new DcClient();

            dcClient.ClientInfo.Description = this.settings.botSettings.botDescription;
            dcClient.ClientInfo.Email = this.settings.botSettings.botEmail;
            dcClient.ClientInfo.Username = this.settings.botSettings.botName;

            if (!string.IsNullOrEmpty(hub.BotName) && hub.OverrideBotName)
            {
                dcClient.ClientInfo.Username = hub.BotName;
            }

            dcClient.ClientInfo.Password = hub.BotPassword;
            dcClient.ClientInfo.Data = hub.Guid;

            try
            {
                string[] host = hub.HubAddress.Split(":".ToCharArray());
                dcClient.ClientInfo.Hostname = host[0];
                dcClient.ClientInfo.Port = Convert.ToInt32(host[1]);
            }
            catch { }

            dcClient.OnConnected += new DcEventHandler(dcClient_OnConnected);
            dcClient.OnDisconnected += new DcEventHandler(dcClient_OnDisconnected);
            dcClient.OnPrivateMessageRecieved += new DcPrivateMessageHandler(dcClient_OnPrivateMessageRecieved);
            dcClient.OnMainchatMessage += new DcUserMessageHandler(dcClient_OnMainchatMessage);
            dcClient.OnMessageReceived += new DcMessageHandler(dcClient_OnMessageReceived);

            return dcClient;
        }

        /// <summary>
        /// Deletes the item.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="guid">The GUID.</param>
        public void DeleteItem(Types type, Guid guid)
        {
            switch (type)
            {
                case Types.Hub:
                    try
                    {
                        clients[guid].Disconnect();
                        clients[guid].Dispose();
                    }
                    catch { }
                    clients.Remove(guid);

                    break;
                case Types.Bulletin:
                    settings.bulletins.Remove(guid);
                    break;
                case Types.Admin:
                    settings.admins.Remove(guid);
                    break;
                case Types.Editor:
                    settings.editors.Remove(guid);
                    break;
                default:
                    break;
            }

            Datahandler dh = new Datahandler();
            dh.SaveSettings(settings);
        }

        /// <summary>
        /// Finds the admin.
        /// </summary>
        /// <param name="UserName">Name of the user.</param>
        /// <param name="hubGuid">The hub GUID.</param>
        /// <returns></returns>
        public Admin FindAdmin(string UserName, Guid hubGuid)
        {
            foreach (Admin admin in settings.admins.Values)
            {
                if (admin.Username == UserName && admin.AccessFrom.Contains(hubGuid))
                {
                    return admin;
                }
            }
            return null;
        }

        /// <summary>
        /// Finds the editor.
        /// </summary>
        /// <param name="UserName">Name of the user.</param>
        /// <param name="hubGuid">The hub GUID.</param>
        /// <returns></returns>
        public Editor FindEditor(string UserName, Guid hubGuid)
        {
            foreach (Editor editor in settings.editors.Values)
            {
                if (editor.UserName == UserName && editor.AccessFrom.Contains(hubGuid))
                {
                    return editor;
                }
            }
            return null;
        }

        #endregion

    }

    #region Enums

    public enum Types 
    { 
        Hub, Bulletin, Admin, Editor
    }

    public enum Access
    {
        Regular, Operator, Editor, Admin
    }

    #endregion

    #region Settings container

    public class Settings
    {
        public BotSettings botSettings = new BotSettings();
        // Lists
        public Dictionary<Guid, Hub> hubs = new Dictionary<Guid, Hub>();
        public Dictionary<Guid, Bulletin> bulletins = new Dictionary<Guid, Bulletin>();
        public Dictionary<Guid, Admin> admins = new Dictionary<Guid, Admin>();
        public Dictionary<Guid, Editor> editors = new Dictionary<Guid, Editor>();
    }

    public class BotSettings
    {
        public string botName = "";
        public string botDescription = "";
        public string botEmail = "";
        public bool autoConnect = false;
        public bool minimizeToTray = false;
        public bool confirmExit = false;
    }

    #endregion
}
